#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <strings.h> // For strncasecmp

// --- Helper Functions (Based on PDF examples) ---

// Error handling function
void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

// Creates the listener socket
int open_listener_socket()
{
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s == -1)
        error("Can't open socket");
    return s;
}

// Binds the socket to the specified port and sets reuse option
void bind_to_port(int socket, int port)
{
    struct sockaddr_in name;
    name.sin_family = PF_INET;
    name.sin_port = (in_port_t)htons(port);
    name.sin_addr.s_addr = htonl(INADDR_ANY);

    int reuse = 1;
    if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(int)) == -1)
        error("Can't set the reuse option on the socket");

    if (bind(socket, (struct sockaddr *)&name, sizeof(name)) == -1)
        error("Can't bind to socket");
}

// Sends a string to the client socket
int say(int socket, char *s)
{
    int result = send(socket, s, strlen(s), 0);
    if (result == -1)
    {
        // Don't exit, just report error for this client
        fprintf(stderr, "%s: %s\n", "Error talking to the client", strerror(errno));
    }
    return result;
}

// Reads a line from the client socket (modified from PDF for simplicity)
int read_in(int socket, char *buf, int len)
{
    memset(buf, 0, len); // Clear buffer
    int bytes_received = recv(socket, buf, len - 1, 0);

    if (bytes_received < 0)
    { // Error
        fprintf(stderr, "Error reading from client: %s\n", strerror(errno));
        return -1;
    }
    else if (bytes_received == 0)
    { // Connection closed
        buf[0] = '\0';
        return 0;
    }
    else
    {
        // Remove trailing \r\n if present
        if (bytes_received >= 2 && buf[bytes_received - 2] == '\r' && buf[bytes_received - 1] == '\n')
        {
            buf[bytes_received - 2] = '\0';
        }
        else if (bytes_received >= 1 && buf[bytes_received - 1] == '\n')
        {
            buf[bytes_received - 1] = '\0';
        }
        // Ensure null termination even if no newline was sent
        buf[bytes_received] = '\0';
    }
    return bytes_received;
}

// Global variable for listener socket descriptor
int listener_d;

// Signal handler for graceful shutdown (Ctrl+C)
void handle_shutdown(int sig)
{
    if (listener_d)
        close(listener_d);

    fprintf(stderr, "Bye!\n");
    exit(0);
}

// Function to set up signal handling
int catch_signal(int sig, void (*handler)(int))
{
    struct sigaction action;
    action.sa_handler = handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    return sigaction(sig, &action, NULL);
}

// --- Main Server Logic ---
int main(int argc, char *argv[])
{
    // Set up signal handler for Ctrl+C
    if (catch_signal(SIGINT, handle_shutdown) == -1)
        error("Can't set the interrupt handler");

    // Create listener socket
    listener_d = open_listener_socket();

    // Bind to port 30000
    bind_to_port(listener_d, 30000);

    // Start listening (queue length 10)
    if (listen(listener_d, 10) == -1)
        error("Can't listen");

    struct sockaddr_storage client_addr;
    unsigned int address_size = sizeof(client_addr);

    puts("Waiting for connection");

    char buf[255]; // Buffer for client input

    // Main server loop
    while (1)
    {
        // Accept a connection
        int connect_d = accept(listener_d, (struct sockaddr *)&client_addr, &address_size);
        if (connect_d == -1)
            error("Can't open secondary socket");

        // Start Knock-Knock protocol
        if (say(connect_d, "Internet Knock-Knock Protocol Server\r\nVersion 1.0\r\nKnock! Knock!\r\n> ") != -1)
        {
            read_in(connect_d, buf, sizeof(buf));
            // Check first response
            if (strncasecmp("Who's there?", buf, 12) != 0)
            {
                say(connect_d, "You should say 'Who's there?'!\r\n");
            }
            else
            {
                // Send first part of joke
                if (say(connect_d, "Oscar\r\n> ") != -1)
                {
                    read_in(connect_d, buf, sizeof(buf));
                    // Check second response
                    if (strncasecmp("Oscar who?", buf, 10) != 0)
                    {
                        say(connect_d, "You should say 'Oscar who?'!\r\n");
                    }
                    else
                    {
                        // Send punchline
                        say(connect_d, "Oscar silly question, you get a silly answer\r\n");
                    }
                }
            }
        }
        // Close the connection with this client
        close(connect_d);
    }

    // This part is unreachable because of the infinite loop and signal handler
    return 0;
}